﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MadProps.ChangeTracking
{
    public class ChangeTracker
    {
        bool ignoreChanges = false;

        public void Subscribe(IPropChangedRaiser raiser)
        {
            raiser.PropChanged += raiser_PropChanged;
        }

        void raiser_PropChanged(object sender, PropChangedEventArgs args)
        {
            if (ignoreChanges)
            {
                return;
            }

            if (args.HasVisited(this))
            {
                return;
            }

            if (Meta.Tracking.Of(args.Prop) != Tracking.Ignored)
            {
                RecordChange(args);
            }
        }

        void RecordChange(PropChangedEventArgs args)
        {
            Redoable.Clear();

            Change current = null;
            if (Undoable.Count > 0)
            {
                current = Undoable.Peek();
            }

            if (current == null || !args.Prop.Is(current.Prop))
            {
                var change = new Change(args.Prop, args.OldValue, args.NewValue);
                Undoable.Push(change);
            }
            else if (object.Equals(current.StartingValue, args.NewValue))
            {
                Undoable.Pop();
            }
            else
            {
                current.EndingValue = args.NewValue;
            }
        }

        Stack<Change> Undoable = new Stack<Change>();
        Stack<Change> Redoable = new Stack<Change>();

        public IEnumerable<Change> UndoStack { get { return Undoable.Where(c => !c.IsTagalong); } }
        public IEnumerable<Change> RedoStack { get { return Redoable.Where(c => !c.IsTagalong); } }

        public void Clear()
        {
            Undoable.Clear();
            Redoable.Clear();
        }

        public void Undo()
        {
            using (IgnoreChanges())
            {
                bool isTagalong = true;
                while (isTagalong)
                {
                    var change = Undoable.Pop();
                    change.Prop.Value = change.StartingValue;
                    Redoable.Push(change);
                    isTagalong = change.IsTagalong;
                }
            }
        }

        public void Redo()
        {
            using (IgnoreChanges())
            {
                bool isTagalong = true;
                while (isTagalong)
                {
                    var change = Redoable.Pop();
                    change.Prop.Value = change.EndingValue;
                    Undoable.Push(change);
                    isTagalong = change.IsTagalong;
                }
            }
        }

        public IDisposable IgnoreChanges()
        {
            return new ChangeIgnorer(this);
        }

        class ChangeIgnorer : IDisposable
        {
            readonly ChangeTracker parent;
            readonly bool ignoreChanges;
            public ChangeIgnorer(ChangeTracker parent)
            {
                this.parent = parent;
                this.ignoreChanges = parent.ignoreChanges;
                parent.ignoreChanges = true;
            }

            public void Dispose()
            {
                parent.ignoreChanges = this.ignoreChanges;
            }
        }

        public string SummarizeChanges()
        {
            var str = new StringBuilder();
            foreach (var change in UndoStack.Reverse())
            {
                string displayName = Meta.DisplayName.Of(change.Prop);

                str.Append(displayName).AppendLine();
            }

            return str.ToString();
        }
    }
}
